package linda.shm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import linda.*;

/** Shared memory implementation of Linda. */
public class CentralizedLinda implements Linda {
	private List<Tuple> memory = Collections
			.synchronizedList(new ArrayList<Tuple>());
	private Map<Tuple, ArrayList<Waiter>[]> waiters = Collections
			.synchronizedMap(new HashMap<Tuple, ArrayList<Waiter>[]>());

	private final static int READER = 0;
	private final static int TAKER = 1;

	public CentralizedLinda() {
	}

	public void write(Tuple t) {
		synchronized (memory) {
			memory.add(t);
		}
		// Parcourir les listes de readers et takers, réveiller le premier
		// threads qui match
		// extraction de toutes les clés du hashmap
		Set<Tuple> keys = waiters.keySet();
		// parcours des valeurs
		for (Tuple tup : keys) {
			if (t.matches(tup)) {
				// réveil de tous les threads en read
				ArrayList<Callback> callbacks = new ArrayList<Callback>();
				synchronized (waiters) {
					Iterator<Waiter> it = waiters.get(tup)[READER].iterator();
					Waiter reader;
					while (it.hasNext()) {
						reader = it.next();

						if (reader instanceof WaiterNotify) {
							synchronized (reader) {
								reader.setTuple(t);
								reader.notify();
							}
						} else {
							callbacks.add(reader.getCallback());
						}
						it.remove();
					}

				}
				for (Callback c : callbacks)
					c.call(t);
				// réveil du premier thread en take, suppression de la
				// liste
				if (waiters.get(tup)[TAKER].size() != 0) {
					Waiter taker = waiters.get(tup)[TAKER].get(0);
					taker.setTuple(t);

					if (taker instanceof WaiterNotify)
						synchronized (taker) {
							taker.notify();
						}
					else {
						taker.getCallback().call(t);
						synchronized (memory) {
							memory.remove(t);
						}
					}
					synchronized (waiters) {
						waiters.get(tup)[TAKER].remove(0);
					}
				}

			}
		}
	}

	public Tuple take(Tuple template) {
		Tuple tuple = getTuple(TAKER, template);

		synchronized (memory) {
			memory.remove(tuple);
		}
		return tuple;
	}

	public Tuple read(Tuple template) {
		return getTuple(READER, template);
	}

	public Tuple tryTake(Tuple template) {
		synchronized (memory) {
			Tuple t;
			if ((t = searchtuple(template)) != null) {
				// il y a le template en memoire
				memory.remove(t);
			}
			return t;
		}
	}

	public Tuple tryRead(Tuple template) {
		synchronized (memory) {
			return (searchtuple(template));
		}
	}

	public Collection<Tuple> takeAll(Tuple template) {
		synchronized (memory) {
			Collection<Tuple> list_tuple;
			list_tuple = searchmultipletuple(template);
			if (!list_tuple.isEmpty()) {
				memory.removeAll(list_tuple);
			}
			return list_tuple;
		}
	}

	public Collection<Tuple> readAll(Tuple template) {
		synchronized (memory) {
			Collection<Tuple> list_tuple;
			list_tuple = searchmultipletuple(template);
			return list_tuple;
		}
	}

	@Override
	public void eventRegister(eventMode mode, eventTiming timing,
			Tuple template, Callback callback) {
		Tuple tuple = null;
		if (timing == eventTiming.IMMEDIATE)
			tuple = searchtuple(template);
		if (tuple != null) { // eventTiming = immediate and tuple found
			if (mode == eventMode.TAKE)
				synchronized (memory) {
					memory.remove(tuple);
				}
			callback.call(tuple);
		} else { // eventTiming = future or (eventTiming = immediate and tuple
					// not found)
			Waiter w = new WaiterCallback();
			int waiter_type = (mode == eventMode.READ) ? READER : TAKER;
			w.setCallback(callback);
			synchronized (waiters) {
				if (waiters.containsKey(template))
					waiters.get(template)[waiter_type].add(w);
				else {
					ArrayList<Waiter> tab[] = new ArrayList[2];
					tab[READER] = new ArrayList<Waiter>();
					tab[TAKER] = new ArrayList<Waiter>();
					tab[waiter_type].add(w);
					waiters.put(template, tab);
				}
			}
		}

	}

	@Override
	public void debug(String prefix) {
		// TODO Auto-generated method stub

	}

	private Tuple getTuple(int waiter_type, Tuple template) {
		// Searching for the template in the memory
		synchronized (memory) {
			for (Tuple t : memory) {
				if (t.matches(template)) {
					if (waiter_type == TAKER)
						memory.remove(t);
					return t;
				}
			}
		}

		Waiter w = new WaiterNotify();

		synchronized (waiters) {
			if (waiters.containsKey(template))
				waiters.get(template)[waiter_type].add(w);
			else {
				ArrayList<Waiter> tab[] = new ArrayList[2];
				tab[READER] = new ArrayList<Waiter>();
				tab[TAKER] = new ArrayList<Waiter>();
				tab[waiter_type].add(w);
				waiters.put(template, tab);
			}
		}

		synchronized (w) {
			try {
				w.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		return w.getTuple();
	}

	/*
	 * methode permettant de retourner un tuple s'il existe en memoire null
	 * sinon
	 */
	private Tuple searchtuple(Tuple template) {
		synchronized (memory) {
			for (Tuple t : memory) {
				if (t.matches(template)) {
					return t;
				}
			}
		}
		return null;
	}

	/*
	 * methode permettant de retourner une collection de tuples s'ils existent
	 * en memoire liste vide sinon
	 */
	private Collection<Tuple> searchmultipletuple(Tuple template) {
		synchronized (memory) {
			Collection<Tuple> list_tuple = new ArrayList<Tuple>();
			for (Tuple t : memory) {
				if (t.matches(template)) {
					list_tuple.add(t);
				}
			}
			return (list_tuple);
		}
	}

}
